// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <deque>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <nlohmann/json.hpp>

#include "coin2/strategy/umm/base.h"
#include "coin2/strategy/umm/fill_stats.h"

namespace xguo {

struct UmmPricerConfig {
  uint64_t product_ids[16];
  int num_products;

  double maker_fee_rate;
  double taker_fee_rate;
  double walk_step;

  int64_t ohlc_window_size;        // usually 20 seconds
  int64_t rate_window_size;        // usually 300 seconds
  int64_t max_nofill_window_size;  // usually 300 seconds

  double risk_threshold;
  double take_profit_rate;
  double stop_loss_rate;
  int64_t force_fire_cooldown_time;
  bool force_on_level;
  int max_level;
  int min_level;

  double price_pull_rate;
  double price_push_rate;
  double max_pos;
  double min_pos;
  double max_fill_stats_pos;
  double min_fill_stats_pos;
  double reserve;
  double lot_size;

  double order_update_period_sec;
  bool use_agg;
  char name[64];

  UmmPricerConfig() { std::memset(this, 0, sizeof(*this)); }

  std::string DebugString() const {
    std::stringstream ss;
    ss << "UmmPricerConfig: "
       << "\n";
    ss << "name: " << name << "\n";

    int n = 0;
    while (product_ids[n] != 0) {
      ss << "product_id: " << product_ids[n] << "\n";
      n++;
    }
    ss << "num_products: " << num_products << "\n";

    ss << "maker_fee_rate: " << maker_fee_rate << "\n";
    ss << "taker_fee_rate: " << taker_fee_rate << "\n";
    ss << "walk_step: " << walk_step << "\n";

    ss << "ohlc_window_size: " << ohlc_window_size << "\n";
    ss << "rate_window_size: " << rate_window_size << "\n";
    ss << "max_nofill_window_size: " << max_nofill_window_size << "\n";

    ss << "risk_threshold: " << risk_threshold << "\n";
    ss << "take_profit_rate: " << take_profit_rate << "\n";
    ss << "stop_loss_rate: " << stop_loss_rate << "\n";
    ss << "force_fire_cooldown_time: " << force_fire_cooldown_time << "\n";
    ss << "force_on_level: " << force_fire_cooldown_time << "\n";
    ss << "max_level: " << max_level << "\n";
    ss << "min_level: " << min_level << "\n";

    ss << "price_pull_rate: " << price_pull_rate << "\n";
    ss << "price_push_rate: " << price_push_rate << "\n";
    ss << "max_pos: " << max_pos << "\n";
    ss << "min_pos: " << min_pos << "\n";
    ss << "max_fill_stats_pos: " << max_fill_stats_pos << "\n";
    ss << "min_fill_stats_pos: " << min_fill_stats_pos << "\n";

    ss << "reserve: " << reserve << "\n";
    ss << "lot_size: " << lot_size << "\n";

    ss << "order_update_period_sec: " << order_update_period_sec << "\n";
    ss << "use_agg: " << use_agg << "\n";
    return ss.str();
  }
};

struct RateEntry {
  RateEntry(int64_t timestamp, double rate) : timestamp(timestamp), rate(rate) {}
  RateEntry() : timestamp(0), rate(0) {}

  bool operator<(const RateEntry& rhs) const { return rate < rhs.rate; }
  double value() const {
    return rate;
  }

  int64_t timestamp;
  double rate;
};

class UmmPricer {
 public:
  explicit UmmPricer(const UmmPricerConfig&);

  const std::string& name() {
    // get name
    return name_;
  }

  bool is_ready() const { return is_bbo_ready(0); }

  bool is_trade(const Bbo& bbo) const {
    // trade
    return bbo.product_id == config_.product_ids[0];
  }

  bool is_ref(const Bbo& bbo) const {
    // ref
    return bbo.product_id != config_.product_ids[0];
  }

  bool is_bbo_ready(int idx) const {
    constexpr int64_t dt = 30'000'000'000LL;
    /*
    DLOG(INFO) << "is_bbo_ready: " << idx << ", " << name_ << ", this " << this << ", "
               << bbo_.at(idx).seqno;
    */
    return bbo_.at(idx).seqno > 20 && timestamp_ - bbo_.at(idx).timestamp < dt;
  }

  bool is_any_ref_ready() const {
    // no reference feed
    if (bbo_.size() == 1) {
      return true;
    }

    for (int n = 1; n < static_cast<int>(bbo_.size()); n++) {
      if (is_bbo_ready(n)) {
        return true;
      }
    }

    return false;
  }

  bool is_agg_cool() const {
    // Agg cool-down time.
    return timestamp_ - last_agg_time_ > config_.force_fire_cooldown_time;
  }

  bool is_fire_cool() const {
    // fire cool-down time.
    return timestamp_ - fill_stats_->last_fill_timestamp() > config_.force_fire_cooldown_time;
  }

  bool is_pos_in_range() const {
    const double pos = fill_stats_->position();
    return pos >= config_.min_pos && pos <= config_.max_pos;
  }

  std::string num_feeds() const {
    std::stringstream ss;
    ss << "num_feeds: [";
    for (const auto& b : bbo_) {
      ss << b.seqno << ", ";
    }
    ss << "]";
    return ss.str();
  }

  const auto& config() const {
    // return config
    return config_;
  }

  void SetMinMaxPos(double min_pos, double max_pos) {
    config_.min_pos = min_pos;
    config_.max_pos = max_pos;
  }

  double last_fill_timestamp() const {
    return fill_stats_->last_fill_timestamp();
  }

  double norm_position() const {
    double pos_range = config_.max_pos - config_.min_pos;
    if (std::abs(pos_range) < 1e-5) {
      return 0.0;
    }

    double n_pos = 2 * (fill_stats_->position() - config_.min_pos) / pos_range - 1;
    if (n_pos > 1) {
       return 1;
    }

    if (n_pos < -1) {
       return -1;
    }

    return n_pos;
  }

  double GetAdjFactorFromFill(int n) {
    /*
    if (n <= 3) {
      return 1.;
    }
    return n / 2.;
    */
    return pow(2, n);
  }

  void UpdateByBbo(const Bbo& bbo);
  void UpdateByFill(const Fill& fill);
  void UpdateByPos(double pos);
  const OrderData& CalculateOrder();
  void TakeProfitOrStopLoss();
  void CalculatePassivePrice();

  const Bbo& trade_bbo() const {
    // bbo
    return bbo_.at(0);
  }

  std::string tag() const {
    if (use_tag_) {
      // to tag string
      nlohmann::json json;
      for (const auto& pair : tags_) {
        json[pair.first] = pair.second;
      }
      return json.dump();
    }
    return "";
  }
  std::string DebugString() const;

  void MakeFillStats() {
    // construct a FillStats
    FillStatsConfig fsconfig;
    fsconfig.maker_fee_rate = config_.maker_fee_rate;
    fsconfig.taker_fee_rate = config_.taker_fee_rate;
    fsconfig.walk_step = config_.walk_step;
    fsconfig.lot_size = config_.lot_size;
    fill_stats_ = std::make_unique<FillStats>(fsconfig);
  }

  void MakeTag() {
    tags_.clear();

    set_tag("ask0", trade_bbo().ask0);
    set_tag("bid0", trade_bbo().bid0);
#if 0
    set_tag("stop_loss_mode", stop_loss_mode_);
    set_tag("position", fill_stats_->position());
    set_tag("pnl", fill_stats_->pnl());
    set_tag("fee", fill_stats_->fee());

    set_tag("buy_lose_qty", fill_stats_->buy_lose_qty());
    set_tag("buy_lose_pnl", fill_stats_->buy_lose_pnl());
    set_tag("buy_profit_qty", fill_stats_->buy_profit_qty());
    set_tag("buy_profit_pnl", fill_stats_->buy_profit_pnl());

    set_tag("sell_lose_qty", fill_stats_->sell_lose_qty());
    set_tag("sell_lose_pnl", fill_stats_->sell_lose_pnl());
    set_tag("sell_profit_qty", fill_stats_->sell_profit_qty());
    set_tag("sell_profit_pnl", fill_stats_->sell_profit_pnl());

    set_tag("exposure_qty", fill_stats_->exposure_qty());
    set_tag("exposure_cost", fill_stats_->exposure_cost());
    set_tag("exposure_avg_price", fill_stats_->exposure_avg_price());
#endif
  }

  void set_tag(const std::string& key, const std::string& value) {
    tags_[key] = value;
  }

  void set_tag(const std::string& key, const char* value) {
    tags_[key] = value;
  }

  void set_tag(const std::string& key, bool value) {
    if (value) {
      tags_[key] = "true";
    } else {
      tags_[key] = "false";
    }
  }

  template<typename T>
  void set_tag(const std::string& key, T value) {
    tags_[key] = std::to_string(value);
  }

 private:
  UmmPricerConfig config_;
  const std::string name_;

  int64_t timestamp_{0};
  int64_t last_agg_time_{0};
  int stop_loss_mode_{0};

  std::unique_ptr<FillStats> fill_stats_;
  std::unique_ptr<OhlcCalculator> ohlc_;
  std::unique_ptr<MovingWindowData<Bbo>> trade_window_;

  std::vector<Bbo> bbo_;
  std::vector<std::unique_ptr<MovingWindowData<RateEntry>>> rate_;
  std::unordered_map<uint64_t, int> pid_to_index_;

  OrderData order_data_;
  std::map<std::string, std::string> tags_;
  bool use_tag_{false};
};

}  // namespace xguo
